package component;

import group.GeoGroup;

import java.util.*;

import javax.media.j3d.Group;
import javax.media.j3d.Transform3D;
import javax.vecmath.Point3d;

import math.Spherical;
import node.GeoNode;
import node.IntersectionValidator;

public abstract class AbstractComponent implements ConnectionListener {

	private final Set<AbstractComponent> connectedComponents = new HashSet<AbstractComponent>();
	
	private final List<ConnectionListener> connectionListeners = new ArrayList<ConnectionListener>();
	
	private GeoNode geoNode = null;
	private boolean isRoot=false;
	private boolean isLocked=false;
	private Status status = new Status();
	
	private final IntersectionValidator validator;

	
	public AbstractComponent(IntersectionValidator validator) {
		this.addConnectionListener(this);
		this.validator = validator;
	}
	

	public boolean isValidPosition() {
		return !validator.intersects(this.geoNode);
	}
	
	public Point3d getPos() {
		return this.geoNode.getPos();
	}
	
	public void setPos(Point3d pos) {
		this.geoNode.setPos(pos);
	}
	
	public double getZOrientation() {
		return 0;
	}
	
	public double getYOrientation() {
		return 0;
	}
	
	@Deprecated
	public void setRotation(double zRotation, double yRotation) {
	}
	
	public void setOrientation(Spherical orientation) {
		
	}
	
	public Spherical getOrientation() {
		return new Spherical();
	}

	public void dispose() {
		this.disconnectAll();
		this.removeAllConnectionListeners();
		this.detachFromScene();
	}
	
	protected void setGeoNode(GeoNode geoNode) {
		this.geoNode = geoNode;
		this.geoNode.setStatus(status);
	}
	
	public void attachToScene(GeoGroup group) {
		group.addChild(geoNode);
	}
	
	public void detachFromScene() {
		this.geoNode.detach();
	}
	
	public boolean isInGroup(Group group) {
		return this.geoNode.getParent() == group;
	}
	
	public void setStatus(Status status) {
		this.status = status;
		this.geoNode.setStatus(status);
	}
	
	public void setStatus(int flag, boolean status) {
		this.status.set(flag, status);
		this.geoNode.setStatus(this.status);
	}

	public void setStatus(int status) {
		this.status.setStatus(status);
		this.geoNode.setStatus(this.status);
	}
	
	public void addConnectionListener(ConnectionListener listener) {
		connectionListeners.add(listener);
	}
	
	public void removeConnectionListener(ConnectionListener listener) {
		connectionListeners.remove(listener);
	}
	
	public void removeAllConnectionListeners() {
		connectionListeners.clear();
	}

	public void connect(AbstractComponent other) {
		if (canConnectTo(other)) {
			connectedComponents.add(other);
			other.connectedComponents.add(this);
			fireConnectedNodes(other);
			other.fireConnectedNodes(this);
		}
	}
	
	public void disconnect(AbstractComponent other) {
		if (isConnectedTo(other)) {
			connectedComponents.remove(other);
			other.connectedComponents.remove(this);
			fireDisconnectedNodes(other);
			other.fireDisconnectedNodes(this);
		}
	}
	
	public void disconnectAll() {
		List<AbstractComponent> components = new ArrayList<AbstractComponent>();
		components.addAll(connectedComponents);
		for (AbstractComponent connectedTo : components) {
			disconnect(connectedTo);
		}
	}

	public boolean isConnectedTo(AbstractComponent other) {
		return connectedComponents.contains(other);
	}
	
	public Set<AbstractComponent> getConnectedComponents() {
		return Collections.unmodifiableSet(connectedComponents);
	}

	public void setRoot(boolean isRoot) {
		this.isRoot = isRoot;
		this.geoNode.setStatus(status);
	}
	
	public boolean isRoot() {
		return isRoot;
	}
	
	public void lock() {
		this.isLocked = true;
	}
	
	public void unlock() {
		this.isLocked = false;
	}
	
	public boolean isLocked() {
		return isLocked;
	}
	
	@Override
	public String toString() {
		return geoNode.toString();
	}
	
	abstract public boolean canConnectTo(AbstractComponent other);
	
	private void fireConnectedNodes(AbstractComponent other) {
		Connection connection = new Connection(this, other);
		for (ConnectionListener listener : connectionListeners)
			listener.componentsConnected(connection);
	}
	
	private void fireDisconnectedNodes(AbstractComponent other) {
		Connection connection = new Connection(this, other);
		for (ConnectionListener listener : connectionListeners)
			listener.componentsDisconnected(connection);
	}
	
	abstract public void applyTransform(Transform3D transform);

}
